package com.airlenet.plugin.maven;

import com.airlenet.plugin.tools.DefaultLaunchScript;
import com.airlenet.plugin.tools.LaunchScript;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.shared.utils.io.FileUtils;
import org.twdata.maven.mojoexecutor.MojoExecutor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import static org.twdata.maven.mojoexecutor.MojoExecutor.*;

@Mojo(name = "repackage", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true,
        requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
        requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class RepackageMojo extends AbstractPackagerMojo {

    private static final Pattern WHITE_SPACE_PATTERN = Pattern.compile("\\s+");
    /**
     * The embedded launch script to prepend to the front of the jar if it is fully
     * executable. If not specified the 'Spring Boot' default script will be used.
     *
     * @since 1.3.0
     */
    @Parameter
    private File embeddedLaunchScript;

    /**
     * Properties that should be expanded in the embedded launch script.
     *
     * @since 1.3.0
     */
    @Parameter
    private Properties embeddedLaunchScriptProperties;
    @Parameter(defaultValue = "true")
    private boolean executable;

    @Parameter(
            defaultValue = "${project.build.outputTimestamp}"
    )
    private String outputTimestamp;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        String classifier = "runnable";

        MojoExecutor.ExecutionEnvironment env = executionEnvironment(mavenProject, mavenSession, pluginManager);
        File jarFile = new File(outputDirectory, finalName + "-" +  classifier + ".jar");

        File libsFolder = new File(distDirectory, "libs");

        // invokes 'maven-dependency-plugin' plugin to copy dependecies to app libs folder
        try {
            executeMojo(
                    plugin(
                            groupId("org.apache.maven.plugins"),
                            artifactId("maven-dependency-plugin"),
                            version("3.1.2")
                    ),
                    goal("copy-dependencies"),
                    configuration(
                            element("excludeArtifactIds","spring-boot-devtools"),
//                            element("excludeScope","provided"),
                            element("outputDirectory", libsFolder.getAbsolutePath())
                    ),
                    env
            );

        } catch (MojoExecutionException e) {
            throw new RuntimeException("Error copying dependencies: " + e.getMessage());
        }
        try {
            executeMojo(
                    plugin(
                            groupId("org.apache.maven.plugins"),
                            artifactId("maven-jar-plugin"),
                            version("3.2.0")
                    ),
                    goal("jar"),
                    configuration(
                            element("classifier", classifier),
                            element("archive",
                                    element("manifest",
                                            element("addClasspath", "true"),
                                            element("classpathPrefix", "libs"),
                                            element("useUniqueVersions", "false"),
                                            element("mainClass", mainClass)
                                    )
//                                    ,
//                                    element("manifestEntries",
//                                            element("Class-Path", ". libs/* plugin/*")
//                                    )
                            ),
                    element("outputDirectory", outputDirectory.getAbsolutePath()),
                            element("finalName", finalName)
                    ),
                    env);

        } catch (MojoExecutionException e) {

            getLog().error("Runnable jar creation failed! " + e.getMessage());
            throw new RuntimeException(e);

        }
//        try {
//            executeMojo(
//                    plugin(
//                            groupId("org.apache.maven.plugins"),
//                            artifactId("maven-assembly-plugin"),
//                            version("3.2.0")
//                    ),
//                    goal("single"),
//                    configuration(
//                            element("archive",
//                                    element("manifest",
//                                            element("mainClass", mainClass)
//                                    )
//                            ),
//                    element("descriptorRefs",element("descriptorRef","jar-with-dependencies")),
//                    element("outputDirectory", outputDirectory.getAbsolutePath()),
//                            element("finalName", finalName)
//                    ),
//                    env);
//
//        } catch (MojoExecutionException e) {
//            getLog().error("Runnable jar creation failed! " + e.getMessage());
//            throw new RuntimeException(e);
//
//        }
        try {


            File target = this.getTargetFile();

            LaunchScript launchScript = getLaunchScript();
            FileOutputStream fileOutputStream = new FileOutputStream(target);
            if (launchScript != null) {
                fileOutputStream.write(launchScript.toByteArray());
                this.setExecutableFilePermission(target);
            }
            FileInputStream fileInputStream = new FileInputStream(jarFile);
            byte[] bs = new byte[1024];
            int length = -1;
            while ((length = fileInputStream.read(bs)) != -1) {
                fileOutputStream.write(bs, 0, length);
            }
            fileOutputStream.close();
            fileInputStream.close();

        } catch (IOException e) {
            getLog().error("Runnable execute jar creation failed! " + e.getMessage());
            throw new RuntimeException(e);
        }


        try {
            FileUtils.copyURLToFile(DefaultLaunchScript.class.getResource("launch.conf"), new File(distDirectory, appName + ".conf"));

            File target = this.getTargetFile();

            String prefix = appName + "-" + mavenProject.getVersion();
            String dest = this.mavenProject.getArtifactId() + "-" + mavenProject.getVersion();
//            ,
//            new Attribute("basedir", outputDirectory.getAbsolutePath())
            executeMojo(
                    plugin(
                            groupId("org.apache.maven.plugins"),
                            artifactId("maven-antrun-plugin"),
                            version("3.0.0")
                    ),
                    goal("run"),
                    configuration(

                            element("target",
                                    element("zip", attributes(new Attribute("destfile", outputDirectory.getAbsolutePath() + "/" + dest + ".zip")
                                            ),
                                            element("zipfileset", attributes(new Attribute("dir", distDirectory.getAbsolutePath() + "/libs"),
                                                    new Attribute("prefix", prefix + "/libs")
                                            )),
                                            element("zipfileset", attributes(new Attribute("dir", distDirectory.getAbsolutePath()),
                                                    new Attribute("includes", target.getName()),
                                                    new Attribute("prefix", prefix),
                                                    new Attribute("filemode", "755"))),
                                            element("zipfileset", attributes(new Attribute("dir", distDirectory.getAbsolutePath()),
                                                    new Attribute("includes", appName+".conf"),
                                                    new Attribute("prefix", prefix),
                                                    new Attribute("filemode", "755")))
                                    ))),
                    env);


        } catch (MojoExecutionException | IOException e) {
            getLog().error("Runnable zip failed! " + e.getMessage());
            throw new RuntimeException(e);
        }

    }

    private File getTargetFile() {

        if (!this.distDirectory.exists()) {
            this.distDirectory.mkdirs();
        }
        return new File(this.distDirectory, this.appName + "." + this.mavenProject.getArtifact().getArtifactHandler().getExtension());
    }

    private FileTime parseOutputTimestamp() {
        return this.outputTimestamp != null && this.outputTimestamp.length() >= 2 ? FileTime.from(this.getOutputTimestampEpochSeconds(), TimeUnit.SECONDS) : null;
    }

    private long getOutputTimestampEpochSeconds() {
        try {
            return Long.parseLong(this.outputTimestamp);
        } catch (NumberFormatException var2) {
            return OffsetDateTime.parse(this.outputTimestamp).toInstant().getEpochSecond();
        }
    }


    private void setExecutableFilePermission(File file) {
        try {
            Path path = file.toPath();
            Set<PosixFilePermission> permissions = new HashSet<>(Files.getPosixFilePermissions(path));
            permissions.add(PosixFilePermission.OWNER_EXECUTE);
            Files.setPosixFilePermissions(path, permissions);
        } catch (Throwable ex) {
            // Ignore and continue creating the jar
        }
    }

    private LaunchScript getLaunchScript() throws IOException {
        if (this.executable || this.embeddedLaunchScript != null) {
            return new DefaultLaunchScript(this.embeddedLaunchScript, buildLaunchScriptProperties());
        }
        return null;
    }

    private Properties buildLaunchScriptProperties() {
        Properties properties = new Properties();
        if (this.embeddedLaunchScriptProperties != null) {
            properties.putAll(this.embeddedLaunchScriptProperties);
        }
        putIfMissing(properties, "initInfoProvides", this.mavenProject.getArtifactId());
        putIfMissing(properties, "initInfoShortDescription", this.mavenProject.getName(), this.mavenProject.getArtifactId());
        putIfMissing(properties, "initInfoDescription", removeLineBreaks(this.mavenProject.getDescription()),
                this.mavenProject.getName(), this.mavenProject.getArtifactId());
        return properties;
    }

    private void putIfMissing(Properties properties, String key, String... valueCandidates) {
        if (!properties.containsKey(key)) {
            for (String candidate : valueCandidates) {
                if (candidate != null && !candidate.isEmpty()) {
                    properties.put(key, candidate);
                    return;
                }
            }
        }
    }

    private String removeLineBreaks(String description) {
        return (description != null) ? WHITE_SPACE_PATTERN.matcher(description).replaceAll(" ") : null;
    }
}
